/*=============================================================================
    jibits/drag.js
    Copyright (C) 2008-2009, Donald W. Griffin
    All rights reserved.
    [MIT license :: see license.txt for details]

    Portions from:

      SortTable
      version 2
      7th April 2007
      Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
      Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
      This basically means: do what you want with it.
      ---
      Dragtable v1.0
      Dan Vanderkam, http://danvk.org/dragtable/
                     http://code.google.com/p/dragtable/
      Licensed under the MIT license.
      ---
      Mike Hall's draggable class (http://www.brainjar.com/dhtml/drag/)
      Do not remove this notice.
      Copyright 2001 by Mike Hall.
      See http://www.brainjar.com for terms of use.

=============================================================================*/
$module("jibits.drag");

$requires("jibits.dom");
$requires("jibits.events");

$namespace(jibits, {

DragType : $enum(["BEGIN", "MOVE", "END"]),

/**
This class manages drag detection and handling for an element. When created, it
registers a mousedown handler. When the mousedown occurs, it registers mousemove
and mouseup handlers to track the activity. As soon as a mousemove is seen where
the mouse has moved a minimum distance from the position of the mousedown, the
drag is initiated. On mouse up, the mousemove and mouseup handlers are removed.

The config object (the 2nd argument to the constructor) has the following
properties:

    handlers - The set of drag event handler methods (onDragBegin/Move/End).

    minRadius - The optional minimum drag radius in pixels. The default is 5.

    dragAlign - The Placement that determines how a dragObj is aligned with the
        associated managed element. If this member is present, but is null, the
        the dragObj will be aligned with the mousedown position. Otherwise, the
        dragObj will be aligned (as specified) with the associated element. By
        default, this is "0%,0%" to align the dragObj with the top/left corner
        of the associated element.

    draggableCSS - The CSS class name to add to element. If present and null,
        no class name will be added. By default, "draggable" will be added.

If a drag is detected, the three handlers given to this object are invoked as
follows:

    onDragBegin (drag);

        This method is called when the drag is first detected. This will be from
        the mousemove handler. This method can optionally return an element to
        be synchronized with the drag. This is often a clone of the object being
        dragged, but can be anything.

    onDragMove (drag);

        This method is called for every mouse movement during the drag. This will
        be from the mousemove handler (obviously). The object that was returned
        by onDragBegin can be retrieved via "drag.mgr.dragObj".

    onDragEnd (drag);

        This method is called when the drag ends. This will be from the mouseup
        handler. The object that was returned by onDragBegin can be retrieved via
        "drag.mgr.dragObj". On return from this call, that object will be removed
        from the DOM. To keep that object, simply set the above property to null
        before returning from this method.

All handlers receive the "drag" object to describe the drag event. The drag object
has the following properties (all coordinates are absolute document coordinates):

       ev - The event object associated with this drag event.
        x - The original mousedown X position.
        y - The original mousedown Y position.
       cx - The current mouse X position.
       cy - The current mouse Y position.
       dx - The delta of current to original mouse X position (cx - x).
       dy - The delta of current to original mouse Y position (cy - y).
      mgr - The DragManager object making the call.
*/
DragManager : $class(function ()
{
    function startDragObj (mgr, drag, dragElem)
    {
        if (dragElem)
        {
            if (mgr.dragAlign)
                mgr.objPos = mgr.dragAlign.anchor(jibits.getTotalOffsetRect(mgr.id));
            else
                mgr.objPos = $point(drag.x, drag.y);

            dragElem.style.position = "absolute";
            dragElem.style.display = "none";

            $assert(!$elem(dragElem.id), "Duplicate ID!");

            document.body.appendChild(dragElem);
            mgr.dragObj = new jibits.Popup(dragElem.id);
            //$log("dragel: " + dragElem.id);

            syncDragObj(mgr, drag);
        }
    }

    function stopDragObj (mgr)
    {
        if (!mgr.dragObj)
            return;

        mgr.dragObj.remove();
        mgr.dragObj = null;
    }

    function syncDragObj (mgr, drag)
    {
        if (!mgr.dragObj)
            return;

        var el = mgr.dragObj.elem();
        el.style.left = (mgr.objPos.x + drag.dx) + "px";
        el.style.top  = (mgr.objPos.y + drag.dy) + "px";
        mgr.dragObj.show();
    }

    return (
    {
        ctor : function (id, config)
        {
            this.id = id;

            var r = ("minRadius" in config) ? config.minRadius : 5;
            this.radius2 = r * r;
            this.handler = config.handlers;

            if ("dragAlign" in config)
            {
                if (config.dragAlign)
                    this.dragAlign = jibits.Placement.from(config.dragAlign);
            }
            else
                this.dragAlign = new jibits.Placement("0%,0%");

            var css = ("draggableCSS" in config) ? config.draggableCSS : "draggable";
            if (css)
                jibits.addClasses(id, [css]);

            this.ticket = jibits.eventMgr().listen(id, "mousedown",
                                                   this.onMouseDown.bind(this));
        },

        destroy : function ()
        {
            $super(arguments, this);
            if (this.ticket)
            {
                this.ticket.destroy();
                delete this.ticket;
            }
        },

        getDrag : function (ev)
        {
            return { x : this.initX,             y : this.initY,
                    cx : ev.pageX,              cy : ev.pageY,
                    dx : ev.pageX - this.initX, dy : ev.pageY - this.initY,
                   mgr : this, ev : ev };
        },

        onMouseDown : function (ev)
        {
            var evmgr = jibits.eventMgr();

            this.dragTicket = evmgr.listen(document, "mousemove",
                                           this.onMouseMove.bind(this), true);
            this.dragTicket.add(evmgr.listen(document, "mouseup",
                                             this.onMouseUp.bind(this), true));
            this.dragging = false;
            this.initX = ev.pageX;
            this.initY = ev.pageY;

            ev.consume();
        },

        onMouseMove : function (ev)
        {
            var drag = this.getDrag(ev);

            if (!this.dragging)
            {
                var d2 = drag.dx*drag.dx + drag.dy*drag.dy;
                if (d2 < this.radius2)
                    return;

                this.dragging = true;
                drag.type = jibits.DragType.BEGIN;
                var dragElem = this.handler.onDragBegin(drag);
                startDragObj(this, drag, dragElem);
            }

            drag.type = jibits.DragType.MOVE;
            syncDragObj(this, drag);
            this.handler.onDragMove(drag);

            ev.consume();
        },

        onMouseUp : function (ev)
        {
            this.dragTicket.destroy();
            delete this.dragTicket;

            if (this.dragging)
            {
                this.dragging = false;
                var drag = this.getDrag(ev);

                drag.type = jibits.DragType.MOVE;
                syncDragObj(this, drag);
                this.handler.onDragMove(drag);

                drag.type = jibits.DragType.END;
                this.handler.onDragEnd(drag);
                stopDragObj(this);

                ev.consume();
            }
        }
    });
}())

}); // namespace jibits
